import WebSocket from "ws"
import fetch from "node-fetch"
import fs from "node:fs"
import moment from "moment"
import getErrorMsg from "./ErrorMsg.js"

export default class Botw {
  #token
  #apiUrl
  #wssUrl
  #ws
  #isStop
  #session

  constructor(config) {
    this.#token = `Bot ${config.appId}.${config.token}`
    this.appId = config.appId
  }

  get user() {
    return this.#session?.user
  }

  get id() {
    return this.#session?.user.id
  }

  get name() {
    return this.#session?.user.username
  }

  get isStop() {
    return this.#isStop
  }

  get stateCode() {
    let code
    if (this.#ws instanceof WebSocket) {
      code = this.#ws.readyState
    }
    return code
  }

  get state() {
    let state
    switch (this.stateCode) {
      case 0:
        state = "连接中..."
        break
      case 1:
        state = "已连接."
        break
      case 2:
        state = "正在断开连接..."
        break
      case 3:
        state = "已断开连接."
        break
      default:
        state = "未连接."
    }
    return state
  }

  #makeLog(...logs) {
    let log = "[QQ频道插件]"
    if (this.name || this.id) log += `[${logger.blue(`${this.name}(${this.id})`)}]`
    return [log, ...logs]
  }

  outLogM(...logs) {
    logger.mark(...this.#makeLog(...logs))
  }

  outLogN(...logs) {
    logger.info(...this.#makeLog(...logs))
  }

  outLogW(...logs) {
    logger.warn(...this.#makeLog(...logs))
  }

  outLogE(...logs) {
    logger.error(...this.#makeLog(...logs))
  }

  outLogD(...logs) {
    logger.debug(...this.#makeLog(...logs))
  }

  send(object) {
    if (this.stateCode === 1) {
      this.#ws.send(JSON.stringify(object))
    }
  }

  stop() {
    this.#isStop = true
    if (typeof this.stateCode === "number")
      this.#ws.terminate()
  }

  async init(config, hMsg) {
    this.#isStop = false

    if (config.sandBox === true) {
      this.#apiUrl = "https://sandbox.api.sgroup.qq.com"
      this.#wssUrl = "wss://sandbox.api.sgroup.qq.com/websocket"
    } else {
      this.#apiUrl = "https://api.sgroup.qq.com"
      this.#wssUrl = "wss://api.sgroup.qq.com/websocket"
    }

    if (config.updateWssUrl === true) {
      let rsp = await this.callApi("/gateway")
      if (rsp?.url) this.#wssUrl = rsp.url
      else this.outLogW("连接地址更新失败！")
    }

    let heartbeatO = 0
    let heartbeatX = 0
    let heartbeatS = 0

    let heartbeatU = () => {
      this.send({ op: 1, d: null })
      this.outLogD("心跳.首次")
    }

    let heartbeatV = () => {
      this.send({ op: 1, d: heartbeatS })
      this.outLogD("心跳.", heartbeatS)
    }

    let isIdentify = true

    let identify = () => {
      this.send({
        op: 2,
        d: {
          token: this.#token,
          intents: config.intents
        }
      })
    }

    let resume = () => {
      this.send({
        op: 6,
        d: {
          token: this.#token,
          session_id: this.#session.session_id,
          seq: heartbeatS
        }
      })
    }

    let startConnect = () => {
      this.#ws = new WebSocket(this.#wssUrl)

      this.#ws.on("error", (error) => {
        this.outLogE("WebSocket 连接错误", error)
      })

      this.#ws.on("close", (code, reason) => {
        if (heartbeatO)
          heartbeatO = clearInterval(heartbeatO)

        switch (code) {
          case 4009:
            isIdentify = false
            this.saveErr("WebSocket通讯", { appId: this.appId, rsp: code, url: this.#wssUrl })
            break
          case 4014:
            this.outLogW("公域机器人，无法开启全部消息.")
            this.#isStop = true
            this.saveErr("WebSocket通讯", { appId: this.appId, rsp: code, url: this.#wssUrl })
            break
          case 4914:
            this.outLogW("机器人已下架，无法连接正式环境.")
            this.#isStop = true
            this.saveErr("WebSocket通讯", { appId: this.appId, rsp: code, url: this.#wssUrl })
            break
          case 4915:
            this.outLogW("机器人已封禁，无法连接.")
            this.#isStop = true
            this.saveErr("WebSocket通讯", { appId: this.appId, rsp: code, url: this.#wssUrl })
            break
          default:
            let err = getErrorMsg(code)
            if (!err) err = "未知"
            this.outLogW(`连接已断开，${err} (${code})`, reason.toString())
            isIdentify = true
            this.saveErr("WebSocket通讯", { appId: this.appId, rsp: code, url: this.#wssUrl })
        }

        if (!this.#isStop)
          setTimeout(startConnect, config.reConInterval)
      })

      this.#ws.on("open", () => {
        this.outLogD("WebSocket 连接成功.")
      })

      this.#ws.on("ping", (data) => {
        this.outLogD("WebSocket Ping.", data)
      })

      this.#ws.on("pong", (data) => {
        this.outLogD("WebSocket Pong.", data)
      })


      this.#ws.on("message", async (data, isBinary) => {
        let msg = JSON.parse(data)

        if (msg.s) heartbeatS = msg.s

        switch (msg.op) {
          case 0:
            switch (msg.t) {
              case "READY":
                await heartbeatU()
                this.#session = msg.d
                // 获取bot信息(部分)
                let botinfo = await this.callApi("/users/@me")
                Bot[botinfo.id] = {
                  uin: botinfo.id,
                  nickname: botinfo.username,
                  avatar: botinfo.avatar,
                  stat: { start_time: Date.now() / 1000, recv_msg_cnt: 0, sent_msg_cnt: 0 },
                  apk: { display: "qq-guild-bot", version: global.__QQGuild__.Version },
                  version: { id: config.allMsg ? "私域" : "公域", name: "qq-guild-bot", version: global.__QQGuild__.Version },
                  fl: new Map,
                  gl: new Map
                }

                if (!Bot.uin2.includes(botinfo.id))
                  Bot.uin2.push(botinfo.id);
                this.outLogN(`已连接`)
                break
              case "RESUMED":
                heartbeatV()
                break
              default:
                await hMsg(msg)
            }
            return
          case 10:
            heartbeatX = msg.d.heartbeat_interval
            if (isIdentify)
              identify()
            else
              resume()
            return
          case 11:
            this.outLogD("心跳反应.", msg)
            if (!heartbeatO)
              heartbeatO = setInterval(heartbeatV, heartbeatX)
            return
          default:
            this.outLogW("收到消息(未处理)", msg)
            break
        }
      })
    }

    startConnect()
  }

  async saveErr(type, data) {
    // 保存请求错误时部分日志
    let code = data.rsp?.code || JSON.stringify(data.rsp)
    let err_data = JSON.parse(fs.readFileSync(global.__QQGuild__.ErrorFile, "utf8"))
    if (!err_data) {
      err_data = {}
      err_data[data.appId] = []
    } else if (!err_data[data.appId])
      err_data[data.appId] = []
    let time = moment(Date.now()).format('YYYY年MM月DD日 HH时mm分ss秒')
    this.outLogD(time, type, data)
    let err_value = [
      `错误类别：${type}`,
      `时间：${time}`,
      `账号：${data.appId}`
    ]
    if (data?.guild_id)
      err_value.push(`频道：${data.guild_id}`)
    if (data?.channel_id)
      err_value.push(`子频道：${data.channel_id}`)
    if (data?.url)
      err_value.push(`链接：${data.url}`)
    err_value.push(`返回错误(码)：${code}`)
    if (data?.trace_id)
      err_value.push(`X-Tps-trace-ID：${data.trace_id}`)
    err_data[data.appId].push(err_value)
    fs.writeFileSync(global.__QQGuild__.ErrorFile, JSON.stringify(err_data, "", "    "), "utf8")
  }

  async callApi(url, option = {}) {
    url = this.#apiUrl + url
    let channel_info = option.channel_info
    delete option.channel_info

    if (!option.headers) option.headers = {}
    option.headers.Authorization = this.#token

    const res = await fetch(url, option).catch(err => this.outLogE("请求失败", err))
    const res_text = await res.text()

    try {
      return JSON.parse(res_text)
    } catch (err) {
      let ret = res_text
      this.outLogE(err)
      this.outLogW("接口返回：", ret)
      let rsp = { code: res.status, message: getErrorMsg(res.status) }
      this.saveErr("请求API", { appId: this.appId, channel_id: channel_info.channel_id, guild_id: channel_info.guild_id, rsp, url, trace_id: res.headers.get('X-Tps-trace-ID') })
      return rsp
    }
  }

  async createDms(source_guild_id, recipient_id) {
    return await this.callApi("/users/@me/dms", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ source_guild_id, recipient_id }) })
  }
}
